/** * Copyright (C) 2010 Orbeon, Inc. * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; either version * 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html */ package org.orbeon.oxf.resources; import org.orbeon.oxf.common.OXFException; import org.orbeon.oxf.common.ValidationException; import org.orbeon.oxf.pipeline.api.TransformerXMLReceiver; import org.orbeon.oxf.xml.*; import org.orbeon.oxf.resources.handler.OXFHandler; import org.orbeon.oxf.xml.XMLParsing; import org.orbeon.oxf.xml.dom4j.LocationData; import org.orbeon.oxf.xml.dom4j.LocationSAXContentHandler; import org.w3c.dom.Node; import org.xml.sax.Locator; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.stream.StreamResult; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Map; /** * Base class for most resource manager implementations. */ public abstract class ResourceManagerBase implements ResourceManager { private static final String MIN_RELOAD_INTERVAL_KEY = "oxf.resources.common.min-reload-interval"; private static final long DEFAULT_MIN_RELOAD_INTERVAL = 2 * 1000; private ExpirationMap lastModifiedMap; /** * Initialisation. Should be called only by sub-classes */ protected ResourceManagerBase(Map props) { // Override default reload interval if property is specified final String minReloadIntervalString = (String) props.get(MIN_RELOAD_INTERVAL_KEY); long minReloadInterval = DEFAULT_MIN_RELOAD_INTERVAL; if (minReloadIntervalString != null) { final long longValue = Long.parseLong(minReloadIntervalString); if (longValue < 0) throw new OXFException("Value for property '" + MIN_RELOAD_INTERVAL_KEY + "' must be a non-negative integer."); minReloadInterval = longValue; } lastModifiedMap = new ExpirationMap(minReloadInterval); } public Node getContentAsDOM(String key) { try { final TransformerXMLReceiver identity = TransformerUtils.getIdentityTransformerHandler(); final DOMResult domResult = new DOMResult(XMLParsing.createDocument()); identity.setResult(domResult); getContentAsSAX(key, identity); return domResult.getNode(); } catch (IllegalArgumentException e) { throw new OXFException(e); } } public org.orbeon.dom.Document getContentAsDOM4J(String key) { final LocationSAXContentHandler lch = new LocationSAXContentHandler(); getContentAsSAX(key, lch); return lch.getDocument(); } public org.orbeon.dom.Document getContentAsDOM4J(String key, XMLParsing.ParserConfiguration parserConfiguration, boolean handleLexical) { final LocationSAXContentHandler lch = new LocationSAXContentHandler(); getContentAsSAX(key, lch, parserConfiguration, handleLexical); return lch.getDocument(); } public void getContentAsSAX(final String key, XMLReceiver handler) { getContentAsSAX(key, handler, XMLParsing.ParserConfiguration.XINCLUDE_ONLY, true); } public void getContentAsSAX(String key, XMLReceiver xmlReceiver, XMLParsing.ParserConfiguration parserConfiguration, boolean handleLexical) { InputStream inputStream = null; final Locator[] locator = new Locator[1]; try { inputStream = getContentAsStream(key); XMLParsing.inputStreamToSAX(inputStream, OXFHandler.PROTOCOL + ":" + key, new ForwardingXMLReceiver(xmlReceiver) { public void setDocumentLocator(Locator loc) { locator[0] = loc; super.setDocumentLocator(loc); } }, parserConfiguration, handleLexical); } catch (ValidationException ve) { throw ve; } catch (ResourceNotFoundException rnfe) { throw rnfe; } catch (Exception e) { if(locator[0] != null) throw new ValidationException("Can't retrieve or parse document for key " + key, e, new LocationData(locator[0])); else throw new OXFException("Can't retrieve or parse document for key " + key, e); } finally { try { if (inputStream != null) inputStream.close(); } catch (IOException e) { throw new OXFException(e); } } } public XMLReceiver getWriteContentHandler(String key) { final OutputStream os = getOutputStream(key); final TransformerXMLReceiver transformer = TransformerUtils.getIdentityTransformerHandler(); transformer.setResult(new StreamResult(os)); return transformer; } final synchronized public long lastModified(String key, boolean doNotThrowResourceNotFound) { // Do only 1 call to currentTimeMillis() final long currentTime = System.currentTimeMillis(); Object value = lastModifiedMap.get(currentTime, key); if (value == null) { // We don't have the information or or it has expired try { long lastModified = lastModifiedImpl(key, doNotThrowResourceNotFound); lastModifiedMap.put(currentTime, key, lastModified); return lastModified; } catch (ResourceNotFoundException e) { lastModifiedMap.put(currentTime, key, e); throw e; } } else { if (value instanceof ResourceNotFoundException) { throw (ResourceNotFoundException) value; } else { return (Long) value; } } } public boolean exists(String key) { try { final InputStream is = getContentAsStream(key); try { is.close(); } catch (IOException e) { // ignore } return true; } catch (ResourceNotFoundException e) { return false; } } abstract protected long lastModifiedImpl(String key, boolean doNotThrowResourceNotFound); }